#include "Mesh.h"

D3D11TPLMesh::D3D11TPLMesh() {
	_VB = NULL;
	_IB = NULL;
	_shadow_VB = NULL;
	_shadow_IB = NULL;
	MAT = NULL;
	TEX = NULL;
	NORM = NULL;
	SPEC = NULL;
	EMIS = NULL;

	vbsize = 0;
	ibsize = 0;

	ngrp = 0;
	nmat = 0;
	ntex = 0;
	nnorm = 0;
	nspec = 0;
	nemis = 0;

	bMixMatAlpha = false;
	bRecomputeBoundings = true;
}

D3D11TPLMesh::D3D11TPLMesh( MESHHANDLE Mesh ) {
	UINT j;
		
	_VB = NULL;
	_shadow_VB = NULL;
	_IB = NULL;
	_shadow_IB = NULL;
	MAT = NULL;
	TEX = NULL;
	NORM = NULL;
	SPEC = NULL;
	EMIS = NULL;

	vbsize = 0;
	ibsize = 0;

	ngrp = 0;
	nmat = 0;
	ntex = 0;
	nnorm = 0;
	nspec = 0;
	nemis = 0;
	bMixMatAlpha = false;
	bRecomputeBoundings = true;

	//load mesh groups
	MESHGROUPEX **grps = NULL;
	ngrp = oapiMeshGroupCount( Mesh );
	//if( !ngrp )		return;		???
	grps = new MESHGROUPEX *[ngrp];
	for( j = 0; j < ngrp; j++ )
		grps[j] = oapiMeshGroupEx( Mesh, j );

	//add materials
	nmat = oapiMeshMaterialCount( Mesh );
	if( nmat ) {
		MAT = new D3D11Material [nmat];
		memset( MAT, 0, sizeof(D3D11Material)*nmat );
		for( j = 0; j < nmat; j++ )
			LoadMaterial( MAT[j], oapiMeshMaterial( Mesh, j ) );
	}

	//add loaded textures
	ntex = oapiMeshTextureCount( Mesh ) + 1;	
	if( ntex ) {
		TEX = new Texture *[ntex];
		NORM = new Texture *[ntex];
		SPEC = new Texture *[ntex];
		EMIS = new Texture *[ntex];
		memset( TEX, 0, sizeof(Texture*)*ntex );
		memset( NORM, 0, sizeof(Texture*)*ntex );
		memset( SPEC, 0, sizeof(Texture*)*ntex );
		memset( EMIS, 0, sizeof(Texture*)*ntex );
		for( j = 1; j < ntex; j++ ) {
			Texture *tex = (Texture*)oapiGetTextureHandle( Mesh, j );
			if( !gc->GetTextureMgr()->Find( tex ) && !gc->GetTextureMgr()->FindGlobal( tex ) )
				tex = NULL;
			TEX[j] = tex;
		}
	}

	//create vertex and index buffers in system memory
	LoadGroups( grps );
	ProcessInherit();
	for( j = 0; j < ngrp; j++ )
		ComputeTangents( &GR[j] );
	FindTransparent();

	SearchForNormalMaps();		//loads all existing normal maps
	for( DWORD j = 0; j < ngrp; j++ )	//compute bounding boxes and spheres for groups
		ComputeBoundingBox( GR[j] );

	delete [ ] grps;
}

D3D11TPLMesh::~D3D11TPLMesh() {
	delete [ ] _VB;
	delete [ ] _shadow_VB;
	delete [ ] _IB;
	delete [ ] _shadow_IB;
	if( MAT )
		delete [ ] MAT;
	if( TEX )
		delete [ ] TEX;
	if( NORM )
		delete [ ] NORM;
	if( SPEC )
		delete [ ] SPEC;
	if( EMIS )
		delete [ ] EMIS;
	delete [ ] GR;
}

void D3D11TPLMesh::LoadGroups( MESHGROUPEX **grps ) {
	UINT j, i;
	DWORD vofs = 0;

	GR = new MGROUP [ngrp];
	memset( GR, 0, sizeof(MGROUP)*ngrp );

	for( j = 0; j < ngrp; j++ ) {
		GR[j].svtx = vbsize;
		GR[j].nvtx = grps[j]->nVtx;

		GR[j].sidx = ibsize;
		GR[j].nidx = grps[j]->nIdx;

		vbsize += GR[j].nvtx;
		ibsize += GR[j].nidx;
	}

	_VB = new MESH_VERTEX [vbsize];
	memset( _VB, 0, sizeof(MESH_VERTEX)*vbsize );
	_IB = new DWORD [ibsize];
	memset( _IB, 0, sizeof(DWORD)*(ibsize) );
	_shadow_VB = new D3DXVECTOR3 [vbsize];
	memset( _shadow_VB, 0, sizeof(D3DXVECTOR3)*vbsize );
	_shadow_IB = new DWORD [ibsize];
	memset( _shadow_IB, 0, sizeof(DWORD)*ibsize );
		
	for( j = 0; j < ngrp; j++ ) {
		for( i = 0; i < GR[j].nvtx; i++ ) {
			_VB[GR[j].svtx + i].pos.x = _shadow_VB[GR[j].svtx + i].x = grps[j]->Vtx[i].x;
			_VB[GR[j].svtx + i].pos.y = _shadow_VB[GR[j].svtx + i].y = grps[j]->Vtx[i].y;
			_VB[GR[j].svtx + i].pos.z = _shadow_VB[GR[j].svtx + i].z = grps[j]->Vtx[i].z;

			_VB[GR[j].svtx + i].norm.x = grps[j]->Vtx[i].nx;
			_VB[GR[j].svtx + i].norm.y = grps[j]->Vtx[i].ny;
			_VB[GR[j].svtx + i].norm.z = grps[j]->Vtx[i].nz;

			_VB[GR[j].svtx + i].tex.x = grps[j]->Vtx[i].tu;
			_VB[GR[j].svtx + i].tex.y = grps[j]->Vtx[i].tv;
		}	
				
		/*for( i = 0; i < GR[j].nidx; i++ )
			_shadow_IB[GR[j].sidx + i] = vofs + grps[j]->Idx[i];
		vofs += GR[j].nvtx;*/

		for(auto id = 0; id < GR[j].nidx; id++)
		{
			_shadow_IB[GR[j].sidx + id] = _IB[GR[j].sidx + id] = grps[j]->Idx[id];
		}
		//memcpy( &_IB[GR[j].sidx], grps[j]->Idx, 2*GR[j].nidx );

		GR[j].mat_idx = grps[j]->MtrlIdx;
		GR[j].tex_idx = grps[j]->TexIdx;

		memcpy( GR[j].tex_idx_ex, grps[j]->TexIdxEx, 4*MAXTEX );
		memcpy( GR[j].tex_mix_idx, grps[j]->TexMixEx, 4*MAXTEX );

		GR[j].userflag = grps[j]->UsrFlag;
		GR[j].scale = GR[j].scale_new = 1.0f;

		D3DXMatrixIdentity( &GR[j].T );
		D3DXMatrixIdentity( &GR[j].T_new );
	}
}

void D3D11TPLMesh::LoadMaterial( D3D11Material &M, const MATERIAL *mat ) {
	M.ambient = D3DXVECTOR3( mat->ambient.r, mat->ambient.g, mat->ambient.b );
	//M.opacity = min( mat->ambient.a, mat->diffuse.a );
	M.opacity = mat->diffuse.a;

	M.diffuse = D3DXVECTOR3( mat->diffuse.r, mat->diffuse.g, mat->diffuse.b );

	M.specular = D3DXVECTOR3( mat->specular.r, mat->specular.g, mat->specular.b );
	M.spec_power = mat->power;

	if (M.spec_power < 0.001f)
	{
		M.spec_power = 1.0f;
	}

	M.emissive = D3DXVECTOR3( mat->emissive.r, mat->emissive.g, mat->emissive.b );

	M.flags = 0;
	M.TexEx_mix = 0.0f;
}

void D3D11TPLMesh::SearchForNormalMaps() {
//Finds _norm mates for all loaded textures
	DWORD ntype;
	char *str;
	char buf[256], nname[256], bname[256];
	Texture *tmp;

	for( UINT j = 1; j < ntex; j++ )
		if( TEX[j] && (str = TEX[j]->GetFileName()) ) {
			strcpy( buf, str );
			UINT len = strlen( buf ), i = 0;
			while( i < len ) {
				if( buf[i] == '.' ) {
					buf[i] = 0;
					i++;
					break;
				}
				else
					i++;
			}
			if( i != len ) {
				if( cfg->NormalMaps ) {
					sprintf( nname, "%s_norm.%s", buf, &buf[i] );
					if( (tmp = gc->GetTextureMgr()->LoadNormalMapFromFile( nname )) ) {
						NORM[j] = tmp;
						nnorm++;
					}
				}
				if( cfg->BumpMaps ) {
					sprintf( bname, "%s_bump.%s", buf, &buf[i] );
					if( (tmp = gc->GetTextureMgr()->LoadBumpMapFromFile( bname ) ) ) {
						NORM[j] = tmp;
						nnorm++;
					}
				}
				if( cfg->SpecularMaps ) {
					sprintf( nname, "%s_spec.%s", buf, &buf[i] );
					if( (tmp = gc->GetTextureMgr()->LoadSpecularMapFromFile( nname )) ) {
						SPEC[j] = tmp;
						nspec++;
					}
				}
				if( cfg->EmissiveMaps ) {
					sprintf( nname, "%s_ems.%s", buf, &buf[i] );
					if( (tmp = gc->GetTextureMgr()->LoadEmissiveMapFromFile( nname )) ) {
						EMIS[j] = tmp;
						nemis++;
					}
				}
			}
		}
}

bool D3D11TPLMesh::FormatNormalFileName(Texture * diffuseTexture, char * nname)
{
	char *str;
	char buf[256];
	if (str = diffuseTexture->GetFileName()) 
	{
		strcpy( buf, str );
		UINT len = strlen( buf ), i = 0;
		while( i < len ) 
		{
			if( buf[i] == '.' ) 
			{
				buf[i] = 0;
				i++;
				break;
			}
			else
				i++;
		}
		if( i != len ) 
		{
			sprintf( nname, "%s_norm.%s", buf, &buf[i] );
			return true;
		}
	}
	return false;
}

void D3D11TPLMesh::ComputeTangents( MGROUP *GR ) {
//Computes the tangent vector for every vertex in a group
	DWORD j;

	if( TEX[GR->tex_idx] ) {	//NULL means untextured group
		D3DXVECTOR3 *ta = new D3DXVECTOR3 [GR->nvtx];
		//bitangent ?

		for( j = 0; j < GR->nvtx; j++ )		//init all tangents
			ta[j] = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );

		GR->nface = GR->nidx/3;
		for( j = 0; j < GR->nface; j++ ) {	//for every triangle
			DWORD
				i0 = _IB[GR->sidx + j*3],
				i1 = _IB[GR->sidx + j*3 + 1],
				i2 = _IB[GR->sidx + j*3 + 2];

			D3DXVECTOR3
				r0 = _VB[GR->svtx + i0].pos,
				r1 = _VB[GR->svtx + i1].pos,
				r2 = _VB[GR->svtx + i2].pos;

			D3DXVECTOR2
				t0 = _VB[GR->svtx + i0].tex,
				t1 = _VB[GR->svtx + i1].tex,
				t2 = _VB[GR->svtx + i2].tex;

			float
				u0 = t1.x - t0.x,
				v0 = t1.x - t0.y,
				u1 = t2.x - t0.x,
				v1 = t2.y - t0.y;

			D3DXVECTOR3
				k0 = r1 - r0,
				k1 = r2 - r0;

			float q = (u0*v1 - u1*v0);
			if( !q )
				q = 1.0f;
			else
				q = 1.0f/q;

			D3DXVECTOR3 t = ((k0*v1 - k1*v0)*q);

			ta[i0] += t;
			ta[i1] += t;
			ta[i2] += t;
		}

		for( j = 0; j < GR->nvtx; j++ ) {
			D3DXVECTOR3 n = _VB[GR->svtx + j].norm;
			D3DXVec3Normalize( &n, &n );

			D3DXVECTOR3 t = (ta[j] - n*D3DXVec3Dot( &ta[j], &n ));

			D3DXVec3Normalize( &t, &t );

			_VB[GR->svtx + j].tang.x = t.x;
			_VB[GR->svtx + j].tang.y = t.y;
			_VB[GR->svtx + j].tang.z = t.z;
		}

		delete [ ] ta;
	}
	else
		for( j = 0; j < GR->nvtx; j++ )
			_VB[GR->svtx + j].tang = D3DXVECTOR3( 1.0f, 0.0f, 0.0f );
}

void D3D11TPLMesh::ProcessInherit() {

	if( GR[0].mat_idx == SPEC_INHERIT )			GR[0].mat_idx = SPEC_DEFAULT;
	if( GR[0].tex_idx == SPEC_INHERIT )			GR[0].tex_idx = SPEC_DEFAULT;
	if( GR[0].tex_idx_ex[0] == SPEC_INHERIT )	GR[0].tex_idx_ex[0] = SPEC_DEFAULT;	

	for( DWORD j = 0; j < ngrp; j++ ) {
		//0x8 flag ????

		//material
		if( GR[j].mat_idx == SPEC_INHERIT )
			GR[j].mat_idx = GR[j-1].mat_idx;

		//texture:
		if( GR[j].tex_idx == SPEC_DEFAULT )
			GR[j].tex_idx = 0;
		else
			if( GR[j].tex_idx == SPEC_INHERIT )
				GR[j].tex_idx = GR[j-1].tex_idx;
			else
				GR[j].tex_idx++;

		//night texture:
		if( GR[j].tex_idx_ex[0] == SPEC_DEFAULT )
			GR[j].tex_idx_ex[0] = 0;
		else
			if( GR[j].tex_idx_ex[0] == SPEC_INHERIT )
				GR[j].tex_idx_ex[0] = GR[j-1].tex_idx_ex[0];
			else
				GR[j].tex_idx_ex[0]++;

		if( GR[j].mat_idx != SPEC_DEFAULT && GR[j].mat_idx >= nmat )
			GR[j].mat_idx = SPEC_DEFAULT;

		if( GR[j].tex_idx >= ntex )
			GR[j].tex_idx = 0;

		if( GR[j].tex_idx_ex[0] >= ntex )
			GR[j].tex_idx_ex[0] = 0;
	}
}

void D3D11TPLMesh::FindTransparent() {
	for( DWORD j = 0; j < ngrp; j++ ) {
		if( !TEX[GR[j].tex_idx] ) {
			if( GR[j].mat_idx == SPEC_DEFAULT )
				GR[j].BlendMode = 2;
			else {
				if( MAT[GR[j].mat_idx].opacity < 0.05f )
					GR[j].BlendMode = 0;
				else
					GR[j].BlendMode = ( MAT[GR[j].mat_idx].opacity < 0.95f ? 1 : 2 );
			}
		}
		else
			GR[j].BlendMode = ( TEX[GR[j].tex_idx]->HasAlpha() ? 1 : 2 );
	}
}

//================================================
//			Mesh Events
//================================================

bool D3D11TPLMesh::SetMeshTexture( DWORD tex_idx, Texture *tex ) {
	if( !tex )				return false;
	if( tex_idx >= ntex )	return false;

	if( TEX[tex_idx] )	gc->GetTextureMgr()->ReleaseTexture( TEX[tex_idx] );
	TEX[tex_idx] = tex;
	gc->GetTextureMgr()->IncrSurfaceRef( tex );
	if( cfg->NormalMaps )
	{
		char nname[250];
		if (FormatNormalFileName(tex, nname))
		{
			if (NORM[tex_idx])
			{
				gc->GetTextureMgr()->ReleaseTexture(NORM[tex_idx]);
				NORM[tex_idx] = nullptr;
			}
			auto normalTex = gc->GetTextureMgr()->LoadNormalMapFromFile(nname);
			if (normalTex)
			{
				NORM[tex_idx] = normalTex;
			}
		}
	}
	return true;
}

int D3D11TPLMesh::SetMeshMaterial( DWORD mat_idx, const MATERIAL *mat ) {
	if( mat_idx >= nmat )	return 4;

	D3D11Material Mat;
	LoadMaterial( Mat, mat );
	memcpy( &MAT[mat_idx], &Mat, sizeof(D3D11Material) );
	return 0;
}

bool D3D11TPLMesh::SetMeshProperty( DWORD prop, DWORD value ) {//!!!!!
	switch( prop ) {
	case MESHPROPERTY_MODULATEMATALPHA:
		bMixMatAlpha = (value != 0 ? 1 : 0);
		return true;
	}
	return false;
}

int D3D11TPLMesh::EditGroup( DWORD grp, GROUPEDITSPEC *ges ) {
	if( grp >= ngrp )	return 1;

	bRecomputeBoundings = true;

	MGROUP &G = GR[grp];
	DWORD flag = ges->flags, j, idx;

	if( flag & GRPEDIT_SETUSERFLAG )		GR[grp].userflag = ges->UsrFlag;
	else if( flag & GRPEDIT_ADDUSERFLAG )	GR[grp].userflag |= ges->UsrFlag;
	else if( flag & GRPEDIT_DELUSERFLAG )	GR[grp].userflag &= ~ges->UsrFlag;

	if( flag & GRPEDIT_VTX ) {

		MESH_VERTEX *data = _VB;
		D3DXVECTOR3 *sdata = _shadow_VB;

		for( j = 0; j < ges->nVtx; j++ ) {
			idx = ( ges->vIdx ? ges->vIdx[j] : j );			
			if( idx < G.nvtx ) {
				idx += G.svtx;

				if( ges->flags & GRPEDIT_VTXCRDX ) {
					data[idx].pos.x = ges->Vtx[j].x;
					sdata[idx].x = ges->Vtx[j].x;
				}
				if( ges->flags & GRPEDIT_VTXCRDY ) {
					data[idx].pos.y = ges->Vtx[j].y;
					sdata[idx].y = ges->Vtx[j].y;
				}
				if( ges->flags & GRPEDIT_VTXCRDZ ) {
					data[idx].pos.z = ges->Vtx[j].z;
					sdata[idx].z = ges->Vtx[j].z;
				}

				if( ges->flags & GRPEDIT_VTXNMLX )	data[idx].norm.x = ges->Vtx[j].nx;
				if( ges->flags & GRPEDIT_VTXNMLY )	data[idx].norm.y = ges->Vtx[j].ny;
				if( ges->flags & GRPEDIT_VTXNMLZ )	data[idx].norm.z = ges->Vtx[j].nz;

				if( ges->flags & GRPEDIT_VTXTEXU )	data[idx].tex.x = ges->Vtx[j].tu;
				if( ges->flags & GRPEDIT_VTXTEXV )	data[idx].tex.y = ges->Vtx[j].tv;
			}
		}

		ComputeTangents( &G );
		ComputeBoundingBox( G );		
	}

	return 0;
}

void D3D11TPLMesh::ComputeBoundingBox( MGROUP &G ) {
	D3DXVECTOR3 min, max, tmp;
	min = max = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
	for( DWORD i = 0; i < G.nvtx; i++ ) {
		tmp = _VB[G.svtx+i].pos;
		
		if( tmp.x < min.x )		min.x = tmp.x;
		if( tmp.y < min.y )		min.y = tmp.y;
		if( tmp.z < min.z )		min.z = tmp.z;

		if( tmp.x > max.x )		max.x = tmp.x;
		if( tmp.y > max.y )		max.y = tmp.y;
		if( tmp.z > max.z )		max.z = tmp.z;
	}

	G.AABB[0] = min;
	G.AABB[1] = max;

	float
		a = G.AABB[0].x - G.AABB[1].x,
		b = G.AABB[0].y - G.AABB[1].y,
		c = G.AABB[0].z - G.AABB[1].z;

	G.bsPos = (G.AABB[0] + G.AABB[1])*0.5f;
	G.bsRad = 0.5f * sqrt( a*a + b*b + c*c );
}